
#include "ast.h"
#include <assert.h>
#include <string.h>
#include <memory.h>
#include <stdlib.h>
#include <stdarg.h>
#include "type.h"
#include "expr.h"
#include "stmt.h"
#include "buffer.h"
#include "option.h"
#include "decl.h"
#include "grammar.h"

#define DUMP_LOG
#ifdef DUMP_LOG
#define dump(x) x
#else
#define dump(x)
#endif

// #define PRINT_PROCESS

#if defined PRINT_PROCESS && defined DEBUG
#define dbg(x) x
#else
#define dbg(...)
#endif

int g_error_count = 0;
ast_node_t* g_errors = nullptr;

void fprint_location(FILE* f, location_t* loc)
{
	fprintf(f, "%s:%03d:%02d: ", loc->filename, loc->line, loc->column);
}

void do_error(location_t* loc, const char *format, ...)
{
	va_list ap;

	g_error_count++;
	if (loc) {
		fprint_location(stderr, loc);
	}
	fprintf(stderr, "error: ");
	va_start(ap, format);
	vfprintf(stderr, format, ap);
	fprintf(stderr, "\n");
	va_end(ap);
}

void do_warning(location_t* loc, const char *format, ...)
{
	va_list ap;
	if (loc) {
		fprint_location(stderr, loc);
	}
	fprintf(stderr, "warning: ");
	va_start(ap, format);
	vfprintf(stderr, format, ap);
	fprintf(stderr, "\n");
	va_end(ap);
}

void expect_token(token_e token)
{
	if (tokc.kind == token)
	{
		token_next();
		return;
	}
    do_error(&tokc.loc, "expect '%s'", get_token_name(token));
    ast_node_t* node = new ast_node_t;
    node->kind = NK_None;
    node->token = tokc;
    append_node(&g_errors, node);
}

token_t tokc;

token_t next_token()
{
    tokc = get_token();
    dbg(print_token(&tokc));
    return tokc;
}

void token_next()
{
    tokc = get_token();
    dbg(print_token(&tokc));
}

void append_node(ast_node_t** root, ast_node_t* node)
{
    if (*root == NULL) {
        *root = node;
        return;
    }
    ast_node_t* last = *root;
    while (last->next) {
        last = last->next;
    }
    last->next = node;
    node->prev = last;
}

void remove_node(ast_node_t** root, ast_node_t* node)
{
    if (*root == NULL) {
        return;
    }
    if (node->prev != nullptr) {
        node->prev->next = node->next;
    }
    if (node->next != nullptr) {
        node->prev = node->prev;
    }
    if (node == *root) {
        *root = node->next;
    }
}

int count_node(ast_node_t* node)
{
    int i = 0;
    while (node) {
        ++i;
        node = node->next;
    }
    return i;
}

void free_node(ast_node_t* node)
{
    if (node == nullptr)
        return;
    switch (node->kind) {
    case NK_None: {
        delete node;
        break;
    }
    case NK_Expression: {
        delete (expr_t*)node;
        break;
    }
    case NK_IfStatement: {
        delete (if_stmt_t*)node;
        break;
    }
    case NK_WhileStatement: {
        delete (while_stmt_t*)node;
        break;
    }
    case NK_ForStatement: {
        delete (for_stmt_t*)node;
        break;
    }
    case NK_CompoundStatement: {
        delete (block_stmt_t*)node;
        break;
    }
    case NK_ExpressionStatement: {
        delete (expr_stmt_t*)node;
        break;
    }
    case NK_Import: {
        delete (import_stmt_t*)node;
        break;
    }
    case NK_METHOD: {
        delete (method_t*)node;
        break;
    }
    case NK_ReturnStatement: {
        delete (return_stmt_t*)node;
        break;
    }
    case NK_Global: {
        delete (global_stmt_t*)node;
        break;
    }
    case NK_BreakStatement: {
        delete (stmt_t*)node;
        break;
    }
    case NK_Declaration: {
        delete (decl_t*)node;
        break;
    }
    case NK_Class: {
        delete (class_def_t*)node;
        break;
    }
    case NK_TranslationUnit:
    case NK_Function:
    //case NK_Declaration:
    case NK_TypeName:
    case NK_Specifiers:
    case NK_Token:
    case NK_TypedefName:
    case NK_EnumSpecifier:
    case NK_Enumerator:
    case NK_StructSpecifier:
    case NK_UnionSpecifier:
    case NK_StructDeclaration:
    case NK_StructDeclarator:
    case NK_PointerDeclarator:
    case NK_ArrayDeclarator:
    case NK_FunctionDeclarator:
    case NK_ParameterTypeList:
    case NK_ParameterDeclaration:
    case NK_NameDeclarator:
    case NK_InitDeclarator:
    case NK_Initializer:
    {
        break;
    }
    default: {
        assert(0 && "unknown node kind to free");
    }
    }
}

void free_nodes(ast_node_t* node)
{
    while (node) {
        auto next = node->next;
        node->next = nullptr;
        free_node(node);
        node = next;
    }
}

int parser_ast(ast_t* ast, const char* buffer, int size, const char* filename/* = NULL */)
{
    // ast_t* ast = (ast_t*)malloc(sizeof(*ast));
    // if (ast)
    //     memset(ast, 0, sizeof(*ast));
    // else
    //     return ast;

    free_ast_errors();
    init_token(buffer, size, filename);
    init_basic_types();
    token_next();
    
    do {
        ast_node_t* node = nullptr;
        if (g_option.decl_first) {
            node = parse_external_decl();
        } else {
            node = parse_statement();
        }
        if (node != nullptr) {
            append_node(&ast->nodes, node);
        } else {
            // try to go on from next line until none
            int line = tokc.loc.line;
            do {
                token_next();
            } while (tokc.kind != TK_NONE && tokc.loc.line == line);
            if (tokc.kind == TK_NONE)
                break;
        }
    } while (tokc.kind != TK_NONE);

    if (get_ast_error_count() > 0) {
        return -get_ast_error_count();
    }
    
    return 0;
}

ast_t::~ast_t()
{
    free_nodes(nodes);
    nodes = nullptr;
    if (buf) {
        free_buffer(buf);
        buf = nullptr;
    }
    free_ast_errors();
}

int ast_count(ast_t* ast)
{
    int i = 0;
    ast_node_t* node = ast->nodes;
    while (node) {
        node = node->next;
        i++;
    }
    return i;
}

void dump_ast(ast_t* ast)
{
    int count = ast_count(ast);
    ast_node_t* node = ast->nodes;
    int i = 1;
    while (node) {
        printf("%d:\n", i);
        print_node(node);
        node = node->next;
        i++;
    }
    printf("ast count: %d\n", count);
    printf("-----------------------------\n");
}

int get_ast_error_count()
{
    return g_error_count;
}

ast_node_t* get_ast_errors()
{
    return g_errors;
}

void free_ast_errors()
{
    ast_node_t* node = g_errors;
    while (node) {
        auto next = node->next;
        delete node;
        node = next;
    }
    g_errors = nullptr;
    g_error_count = 0;
}

static void print_tab(int tab)
{
    for (int i = 0; i < tab; ++i)
    {
        printf("    ");
    }
}

void do_print_node(ast_node_t* node, int tab, bool s)
{
    print_tab(tab);
    if (node->kind >= NK_ExpressionStatement && node->kind <= NK_StatementEnd) {
        if (!s)
            print_statement(node);
        else
            print_statement_s(node);
    } else if (node->kind == NK_Expression) {
        if (!s)
            print_expression(node);
        else
            print_expression_s(node);
    } else if (node->kind < NK_Expression) {
        print_decl(node);
    } else {
        assert(0 && "printing unexpected nodes");
    }
}

void print_node(ast_node_t* node)
{
    do_print_node(node, 0, false);
}

void print_node_s(ast_node_t* node)
{
    do_print_node(node, 0, true);
}

/**
 * Check if current token is in a token set
 */
int current_token_in(int toks[])
{
	int *p = toks;

	while (*p)
	{
		if (tokc.kind == *p)
			return 1;
		p++;
	}

	return 0;
}

static int FIRST_Declaration[] = { FIRST_DECLARATION, 0};

bool is_token_decl(int kind)
{
	int *p = FIRST_Declaration;

	while (*p)
	{
		if (kind == *p)
			return true;
		p++;
	}

	return false;
}

bool is_decl(ast_node_t* node)
{
	return node->kind < NK_Expression;
}

ast_node_t* parse_node()
{
    if (tokc.kind == TK_ID && !is_typedef(str(&tokc))) {
        return parse_statement();
    } else if (is_token_decl(tokc.kind)) {
        return parse_decl();
    } else {
        return parse_statement();
    }
}